package edu.ufl.cise.cop5555.sp12.context;

import java.util.Iterator;
import java.util.List;

import edu.ufl.cise.cop5555.sp12.Kind;
import edu.ufl.cise.cop5555.sp12.TokenStream.IllegalStringLiteralException;
import edu.ufl.cise.cop5555.sp12.TokenStream.Token;
import edu.ufl.cise.cop5555.sp12.ast.AST;
import edu.ufl.cise.cop5555.sp12.ast.ASTVisitor;
import edu.ufl.cise.cop5555.sp12.ast.AssignExprCommand;
import edu.ufl.cise.cop5555.sp12.ast.AssignPairListCommand;
import edu.ufl.cise.cop5555.sp12.ast.BinaryOpExpression;
import edu.ufl.cise.cop5555.sp12.ast.Block;
import edu.ufl.cise.cop5555.sp12.ast.BooleanLiteralExpression;
import edu.ufl.cise.cop5555.sp12.ast.CompoundType;
import edu.ufl.cise.cop5555.sp12.ast.DecOrCommand;
import edu.ufl.cise.cop5555.sp12.ast.Declaration;
import edu.ufl.cise.cop5555.sp12.ast.DoCommand;
import edu.ufl.cise.cop5555.sp12.ast.DoEachCommand;
import edu.ufl.cise.cop5555.sp12.ast.ExprLValue;
import edu.ufl.cise.cop5555.sp12.ast.IfCommand;
import edu.ufl.cise.cop5555.sp12.ast.IfElseCommand;
import edu.ufl.cise.cop5555.sp12.ast.IntegerLiteralExpression;
import edu.ufl.cise.cop5555.sp12.ast.LValueExpression;
import edu.ufl.cise.cop5555.sp12.ast.Pair;
import edu.ufl.cise.cop5555.sp12.ast.PairList;
import edu.ufl.cise.cop5555.sp12.ast.PrintCommand;
import edu.ufl.cise.cop5555.sp12.ast.PrintlnCommand;
import edu.ufl.cise.cop5555.sp12.ast.Program;
import edu.ufl.cise.cop5555.sp12.ast.SimpleLValue;
import edu.ufl.cise.cop5555.sp12.ast.SimpleType;
import edu.ufl.cise.cop5555.sp12.ast.StringLiteralExpression;
import edu.ufl.cise.cop5555.sp12.ast.Type;
import edu.ufl.cise.cop5555.sp12.ast.UnaryOpExpression;


public class ContextCheckVisitor  implements ASTVisitor {

	Token prog_name;
	SymbolTable symbol_table;
	public ContextCheckVisitor() {
		// TODO Auto-generated constructor stub
		symbol_table = new SymbolTable();	
	}
	
	boolean uniqueProgName(Token ident)
	{
		try {
			if(ident.getText().equals(prog_name.getText()))
			{
				return false;
			}
			else
				return true;
		} catch (IllegalStringLiteralException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
		
	}
	
	void check(boolean condition, AST node, String message) throws ContextException
	{
		if(condition == false)
		{
			ContextException ce = new ContextException(node, message);
			throw ce;
		}
	}
	
	@Override
	public Object visitProgram(Program program, Object arg) throws Exception {
		// TODO Auto-generated method stub
		//IDENT is program name and cannot be used as variable name
		prog_name= program.ident;
		program.block.visit(this, arg);			
		return null;
	}

	@Override
	public Object visitBlock(Block block, Object arg) throws Exception {
		// TODO Auto-generated method stub
		//Each block delineates a scope.. enter scope
		symbol_table.enterScope();
		
		if (!block.decOrCommands.isEmpty()) {
			
			for (DecOrCommand cd : block.decOrCommands) {
				cd.visit(this, arg);
				
			}
		}
		//exit scope
		symbol_table.exitScope();
		return null;
	}

	@Override
	public Object visitDeclaration(Declaration declaration, Object arg)
			throws Exception {
		//IDENT doesnot belong to current scope
		String str_ident = declaration.ident.getText();
		declaration.type.visit(this, arg);
		
		if(!uniqueProgName(declaration.ident))
		{
			ContextException ce = new ContextException(declaration, "Cannot use same name as PROG Identifier");
			throw ce;	
			
			
		}
		boolean isIdentPresent = symbol_table.insert(declaration.ident.getText(), declaration);
		declaration.ident.setScope(symbol_table.current_scope);
		if(isIdentPresent == false)
			{
			ContextException ce = new ContextException(declaration, "Duplicate IDENT "+declaration.ident.getText()+" in the current scope");
			throw ce;
			}
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object visitSimpleType(SimpleType simpleType, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		//type must be int, bool or string
		if(!simpleType.isTypeValid())
		{
			ContextException ce = new ContextException(simpleType, "Simple type is not INT , BOOLEAN or STRING");
			throw ce;
			
		}
		return simpleType;
	}

	@Override
	public Object visitCompoundType(CompoundType compoundType, Object arg)
			throws Exception {
		
		/*<CompoundType>.type = (keyType, valType) where
			keyType = <SimpleType>.type
			valType = <Type>.type
		 */
		compoundType.keyType.visit(this, arg);
		compoundType.valType.visit(this, arg);	
				
		// TODO Auto-generated method stub
		
		return compoundType;
	}

	
	@Override
	public Object visitAssignExprCommand(AssignExprCommand assignExprCommand,
			Object arg) throws Exception {
		Type lhsType = (Type) assignExprCommand.lValue.visit(this,arg);
		Type exprType = (Type) assignExprCommand.expression.visit(this,arg);
		check(lhsType.equals(exprType), assignExprCommand,"incompatible types in assignment");
		return null;
	}

	@Override
	public Object visitAssignPairListCommand(
			AssignPairListCommand assignPairListCommand, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		Type lvalueType = (Type)assignPairListCommand.lValue.visit(this, arg);
		Type pairListType = (Type)assignPairListCommand.pairList.visit(this, arg);
		if(pairListType != null)
		{
		
			if(lvalueType instanceof CompoundType && pairListType instanceof CompoundType)
			{
				CompoundType c1 = (CompoundType) lvalueType;
				CompoundType c2 = (CompoundType) pairListType;
				if(c1.keyType.equals(c2.keyType) && c1.valType.equals(c2.valType))
				{
					return null;
				}
				else
				{
					//error
					ContextException ce = new ContextException(assignPairListCommand, "AssignPairListCommand - Key or Val Types dont match");
					throw ce;
				}
				
			}
			else
			{
				//error
				ContextException ce = new ContextException(assignPairListCommand, "AssignPairListCommand - Types are not compounf type");
				throw ce;
			}
			
			
		}
		else  
			return null;
	}

	@Override
	public Object visitPrintCommand(PrintCommand printCommand, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		printCommand.expression.visit(this, arg);
		
		return null;
	
	}

	@Override
	public Object visitPrintlnCommand(PrintlnCommand printlnCommand, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		printlnCommand.expression.visit(this, arg);
		return null;

	}

	@Override
	public Object visitDoCommand(DoCommand doCommand, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		//Condition: <Expression>.type == boolean
		Type exprType = (Type)doCommand.expression.visit(this, arg);
		Type boolTemp = new SimpleType(Kind.BOOLEAN);
		if(!exprType.equals(boolTemp))
		{
			ContextException ce = new ContextException(doCommand, "DoCommand - condition should evaluate to boolean");
			throw ce;
		}
		else
			doCommand.block.visit(this, arg);
		return null;
	}

	@Override
	public Object visitDoEachCommand(DoEachCommand doEachCommand, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		Type lvalueType = (Type) doEachCommand.lValue.visit(this, arg);
		if(lvalueType instanceof CompoundType)
		{
		
			
			Declaration d1 = symbol_table.lookup(doEachCommand.key.getText());
			Declaration d2 = symbol_table.lookup(doEachCommand.val.getText());
			if(d1 != null && d2!= null)
			{	
			check(d1.type.equals(((CompoundType) lvalueType).keyType), doEachCommand, "Key types dont match");
			check(d2.type.equals(((CompoundType) lvalueType).valType), doEachCommand, "Val types don't match");
			doEachCommand.key.scopeNum = d1.ident.scopeNum;
			doEachCommand.val.scopeNum = d2.ident.scopeNum;
			
			//check(doEachCommand.key.equals(keyKind), doEachCommand , "Do each command - Key types dont match");
			//TODO TA reply
			doEachCommand.block.visit(this, arg);
			}
			else
			{
				ContextException c = new ContextException(doEachCommand, "Do Each - No declaration for "+doEachCommand.key.getText()+ " found");
				throw c;
			}
			
		}
		else
		{
			ContextException c = new ContextException(doEachCommand, "Do Each - Lvalue must be Compound Type");
			throw c;
		}
		return null;
	}

	@Override
	public Object visitIfCommand(IfCommand ifCommand, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		Type exprType = (Type)ifCommand.expression.visit(this, arg);
		Type boolTemp = new SimpleType(Kind.BOOLEAN);
		check(exprType.equals(boolTemp) , ifCommand, "If Command - expression must be boolean");
		ifCommand.block.visit(this, arg);
		return null;
	}

	@Override
	public Object visitIfElseCommand(IfElseCommand ifElseCommand, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		Type exprType = (Type)ifElseCommand.expression.visit(this, arg);
		Type boolTemp = new SimpleType(Kind.BOOLEAN);
		check(exprType.equals(boolTemp) , ifElseCommand, "If Command - expression must be boolean");
		ifElseCommand.ifBlock.visit(this, arg);
		ifElseCommand.elseBlock.visit(this, arg);
		return null;
		
	}

	@Override
	public Object visitSimpleLValue(SimpleLValue simpleLValue, Object arg)
			throws Exception {
		//IDENTIFIER belongs to symbol table and is in current scope
	Declaration d = symbol_table.lookup(simpleLValue.identifier.getText());
	
	
	//got in scope declaration of IDENTIFIER
	if(d != null)
	{
		int scope = d.ident.scopeNum;
		simpleLValue.identifier.setScope(scope);
		
		simpleLValue.typebase = d.type;
		//check(simpleLValue.identifier.equals(d.ident) , simpleLValue, "Type of SimpleLValue does not match ");
		return d.type;
		
	}
	else
	{
		ContextException ce = new ContextException(simpleLValue, "No IDENT  "+simpleLValue.identifier.getText()+" in current scope");
		throw ce;
	}
	
	
	}

	@Override
	public Object visitExprLValue(ExprLValue exprLValue, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		
		//IDENTIFIER belongs to symbol table and is in current scope
		
		//TODO
		Declaration d = symbol_table.lookup(exprLValue.identifier.getText());
		
		if(d!= null)
		{
			
			
			if(!(d.type instanceof  CompoundType))
			{
				ContextException ce = new ContextException(exprLValue, "Type is not a compound type");
				throw ce;
				
			}
			else
			{
				CompoundType ct = (CompoundType)d.type;
				exprLValue.identifier.scopeNum = d.ident.scopeNum;
				exprLValue.typebase = ct;
				Type keyType = (Type)exprLValue.expression.visit(this, arg);
				check(ct.keyType.equals(keyType), exprLValue,"ExprLValue -Key type and expression key type dont match");
				return ct.valType;
			}
		
			
			
			
		}
		else
		{
			ContextException ce = new ContextException(exprLValue, "No IDENT  "+exprLValue.identifier.getText()+" in current scope");
			throw ce;
		}
		
		
	}

	@Override
	public Object visitPair(Pair pair, Object arg) throws Exception {
		// TODO Auto-generated method stub
		Type expr0Type = (Type) pair.expression0.visit(this, arg);
	    Type expr1Type = (Type) pair.expression1.visit(this, arg);
	    return new CompoundType((SimpleType)expr0Type, expr1Type);
	}

	@Override
	public Object visitPairList(PairList pairList, Object arg) throws Exception {
		// TODO Auto-generated method stub
		Pair prev = null;
		Type keyType = null;
		Type valType = null;
		if(pairList.pairs.isEmpty())
			return null;
		for (Pair p : pairList.pairs) {
					
			if(prev == null )
			{
				prev = p;
				keyType = (Type) p.expression0.visit(this, arg);
				valType = (Type)p.expression1.visit(this, arg);
				
			}
			else{
			keyType = (Type) prev.expression0.visit(this, arg);
			valType = (Type)prev.expression1.visit(this, arg);
			Type t1 = (Type) p.expression0.visit(this, arg);
			Type t2 = (Type)p.expression1.visit(this, arg);

			check(keyType.equals(t1), pairList,"PairList - expression 0 type doesn't match" );
			check(valType.equals(t2), pairList, "PairList - expression 1 type doesnt match");
			prev = p;
			}
			
			
		}
		if(keyType instanceof SimpleType)
			{Type retType = new CompoundType((SimpleType)keyType, valType);
			return retType;
			}
		else
			return null;
		
	}

	@Override
	public Object visitLValueExpression(LValueExpression lValueExpression,
			Object arg) throws Exception {
			
		Type t = (Type)lValueExpression.lValue.visit(this, arg);
		lValueExpression.typeBase = t;
		return t;
	}

	@Override
	public Object visitIntegerLiteralExpression(
			IntegerLiteralExpression integerLiteralExpression, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		if(integerLiteralExpression.integerLiteral.kind != Kind.INTEGER_LITERAL)
		{
			
			ContextException c = new ContextException(integerLiteralExpression, "Integer Literal doesnt have type INT");
			throw c;
		}
		else
		{
			Type t = new SimpleType(Kind.INT);
			integerLiteralExpression.typeBase = t;
			return t;
		}
		
		
	}

	@Override
	public Object visitBooleanLiteralExpression(
			BooleanLiteralExpression booleanLiteralExpression, Object arg)
			throws Exception {
		if(booleanLiteralExpression.booleanLiteral.kind != Kind.BOOLEAN_LITERAL)
		{
			ContextException c = new ContextException(booleanLiteralExpression, "Boolean Literal doesnt have type boolean");
			throw c;
		}
		else
		{
			Type t = new SimpleType(Kind.BOOLEAN);
			booleanLiteralExpression.typeBase = t;
			return t;
		}
	
	}

	@Override
	public Object visitStringLiteralExpression(
			StringLiteralExpression stringLiteralExpression, Object arg)
			throws Exception {
		// TODO Auto-generated method stub
		
		if(stringLiteralExpression.stringLiteral.kind != Kind.STRING_LITERAL)
		{
			ContextException c = new ContextException(stringLiteralExpression, "String Literal doesnt have type String");
			throw c;
		}
		else
		{
			Type t = new SimpleType(Kind.STRING);
			stringLiteralExpression.typeBase = t;
			return t;
		}
	}

	@Override
	public Object visitUnaryOpExpression(UnaryOpExpression unaryOpExpression,
			Object arg) throws Exception {
		// TODO Auto-generated method stub
		Type t = (Type)unaryOpExpression.expression.visit(this, arg);
		unaryOpExpression.typeBase = t;
		
		
		if(unaryOpExpression.op != Kind.MINUS &&   unaryOpExpression.op != Kind.NOT )
		{
			ContextException c = new ContextException(unaryOpExpression, "UnaryOpExpression type does not match MINUS or NOT");
			throw c;
		}
		else if(unaryOpExpression.op == Kind.MINUS)
		{
			
			if(t instanceof SimpleType)
			{
				
				SimpleType s = (SimpleType) t;
				if(s.type != Kind.INT)
				{
					ContextException ce = new ContextException(unaryOpExpression, "Unary op expression type is not INT ");
					throw ce;	
				}
				else
				{
					return s;
				}
				
				
			}
			else
			{
				ContextException c = new ContextException(unaryOpExpression, "Unary op expression type is not a Simple Type (int or boolean");
				throw c;	
			}
			
			
			
			
		}
		else if(unaryOpExpression.op == Kind.NOT)
		{
			
			if(t instanceof SimpleType)
			{
				
				SimpleType s = (SimpleType) t;
				if(s.type != Kind.BOOLEAN)
				{
					ContextException ce = new ContextException(unaryOpExpression, "Unary op expression type is not boolean");
					throw ce;	
				}
				else
					return s;
				
				
			}
			else
			{
				ContextException c = new ContextException(unaryOpExpression, "Unary op expression type is not a Simple Type (int or boolean");
				throw c;	
			}
			
			
		}
		else
		{
			return null;
		}
		
		
	}

	@Override
	public Object visitBinaryOpExpression(BinaryOpExpression binaryOpExpression, Object arg) throws Exception {
		// TODO Auto-generated method stub
	//Type rules for BinaryOpExpression
	Type t0 = (Type)binaryOpExpression.expression0.visit(this, arg);
	Type t1 = (Type)binaryOpExpression.expression1.visit(this, arg);
	//SimpleType stype = null;
	Type intTemp = new SimpleType(Kind.INT);
	Type stringTemp = new SimpleType(Kind.STRING);
	Type boolTemp = new SimpleType(Kind.BOOLEAN);
	
	//Rule 1
	//binaryOpExpression.expression0.typeBase = t0;
	//binaryOpExpression.expression1.typeBase = t1;
	
	if(binaryOpExpression.op == Kind.PLUS)
	{
		if(t0.equals(t1) )
		{
			if(t0.equals(boolTemp))
			{
				ContextException c = new ContextException(binaryOpExpression, "Operator + cannot be applied to boolean types");
				throw c;
			}
			else
			{
				binaryOpExpression.typeBase = t0;
				return t0;
				
			}
		}
		else if((t0.equals(stringTemp)|| t1.equals(stringTemp))&& ( t0.equals(intTemp) || t0.equals(boolTemp) || t1.equals(intTemp) || t1.equals(boolTemp)))
		{
			binaryOpExpression.typeBase = stringTemp;
			return stringTemp;
			
		}
		else
		{
			ContextException c = new ContextException(binaryOpExpression, "Operator + cannot be applied to incompatible types");
			throw c;
		}
		

	}
	else
	{
		check(t0.equals(t1), binaryOpExpression, "BinaryOpExpression - Both types must be same ");
	
		if(binaryOpExpression.op == Kind.EQUALS || binaryOpExpression.op== Kind.NOT_EQUALS || binaryOpExpression.op== Kind.GREATER_THAN || binaryOpExpression.op == Kind.LESS_THAN || binaryOpExpression.op==Kind.AT_LEAST || binaryOpExpression.op==Kind.AT_MOST)
		{
			
			binaryOpExpression.typeBase = boolTemp;
			return boolTemp;
			
		}
		else if(binaryOpExpression.op == Kind.TIMES || binaryOpExpression.op == Kind.MINUS)
		{
			//Can be applied to integers and maps
			if(t0.equals(intTemp) || t0 instanceof CompoundType)
			{
				binaryOpExpression.typeBase = t0;
				return t0;
			}
			else if(t1.equals(intTemp) || t1 instanceof CompoundType)
			{
				binaryOpExpression.typeBase = t1;
				return t1;
			}
			else
			{
				ContextException ce = new ContextException(binaryOpExpression, "TIMES, MINUS can only be applied to integers or maps");
				throw ce;
			}
			
			
		}
		else if(binaryOpExpression.op == Kind.DIVIDE)
		{
			
			if(t0.equals(intTemp) && t1.equals(intTemp))
			{
				binaryOpExpression.typeBase = intTemp;
				return intTemp;
			}
			else
			{
				ContextException ce = new ContextException(binaryOpExpression, "Divide can only be applied to integer ");
				throw ce;
			}
		}
		else if(binaryOpExpression.op == Kind.AND || binaryOpExpression.op == Kind.OR)
		{
			if(t0.equals(t1) && t0.equals(boolTemp))
			{
				binaryOpExpression.typeBase = boolTemp;
				return boolTemp;
			}
			else
			{
				ContextException ce = new ContextException(binaryOpExpression, "AND , OR can only be applied to booleans ");
				throw ce;
			}
		}
		else
		{
			ContextException ce = new ContextException(binaryOpExpression, " ");
			throw ce;
			
		}
	
		
		
	}
	
	}
	
	
}